﻿//
// (C) Copyright 2009 Irantha Suwandarathna (iranthas@hotmail.com)
// All rights reserved.
//

/* Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using EffiProz.Core.Lib;
using EffiProz.Core.Errors;
using EffiProz.Core.Store;
using EffiProz.Core.Lib.IO;

namespace EffiProz.Core.Persist
{
    public class RAShadowFile
    {
        Database database;
        String pathName;
        Storage source;
        FileStream dest;
        int pageSize;
        long maxSize;
        BitMap bitMap;
        bool zeroPageSet;
        ByteArrayOutputStream byteArrayOutputStream =
            new ByteArrayOutputStream(new byte[] { });

        public RAShadowFile(Database database, Storage source, String pathName,
                     long maxSize, int pageSize)
        {

            this.database = database;
            this.pathName = pathName;
            this.source = source;
            this.pageSize = pageSize;
            this.maxSize = maxSize;

            int bitSize = (int)(maxSize / pageSize);

            if (maxSize % pageSize != 0)
            {
                bitSize++;
            }

            bitMap = new BitMap(bitSize);
        }

        public void copy(long fileOffset, int size)
        {

            // always copy the first page
            if (!zeroPageSet)
            {
                copy(0);
                bitMap.set(0);

                zeroPageSet = true;
            }

            if (fileOffset >= maxSize)
            {
                return;
            }

            long endOffset = fileOffset + size;
            int startPageOffset = (int)(fileOffset / pageSize);
            int endPageOffset = (int)(endOffset / pageSize);

            if (endOffset % pageSize == 0)
            {
                endPageOffset--;
            }

            for (; startPageOffset <= endPageOffset; startPageOffset++)
            {
                copy(startPageOffset);
            }
        }

        private void copy(int pageOffset)
        {

            if (bitMap.set(pageOffset) == 1)
            {
                return;
            }

            long position = (long)pageOffset * pageSize;
            int readSize = pageSize;

            if (maxSize - position < pageSize)
            {
                readSize = (int)(maxSize - position);
            }

            try
            {
                if (dest == null)
                {
                    open();
                }

                long writePos = dest.Length;
                byte[] buffer = new byte[pageSize + 12];

                byteArrayOutputStream.setBuffer(buffer);
                byteArrayOutputStream.writeInt(pageSize);
                byteArrayOutputStream.writeLong(position);
                source.seek(position);
                source.read(buffer, 12, readSize);
                dest.Seek(writePos, SeekOrigin.Begin);
                dest.Write(buffer, 0, buffer.Length);
            }
            catch (Exception t)
            {
                bitMap.unset(pageOffset);
                close();
                database.logger.logWarningEvent("pos" + position + " " + readSize,
                                                t);

                throw new IOException(t.Message, t);
            }
            finally { }
        }

        private void open()
        {
            dest = new FileStream(pathName, FileMode.OpenOrCreate, FileAccess.ReadWrite);
        }

        /**
         * Called externally after a series of copy() calls.
         * Called internally after a restore or when error in writing
         */
        public void close()
        {

            if (dest != null)
            {
                dest.Close();

                dest = null;
            }
        }

        /** todo - take account of incomplete addition of block due to lack of disk */

        // buggy database files had size == position == 0 at the end
        public static void restoreFile(String sourceName,
                                       String destName)
        {

            FileStream source = new FileStream(sourceName, FileMode.Open, FileAccess.Read);
            FileStream dest = new FileStream(destName, FileMode.OpenOrCreate, FileAccess.ReadWrite);
            BinaryReader srcReader = new BinaryReader(source);

            while (source.Position != source.Length)
            {
                int size = srcReader.ReadInt32();
                long position = srcReader.ReadInt64();
                byte[] buffer = new byte[size];

                source.Read(buffer, 0, size);
                dest.Seek(position, SeekOrigin.Begin);
                dest.Write(buffer, 0, size);
            }
            srcReader.Close();
            dest.Close();
        }
    }
}
